<?php
/**
 * select-expression-processor.php
 *
 * This file implements the processor for SELECT expressions.
 *
 * Copyright (c) 2010-2012, Justin Swanhart
 * with contributions by André Rothe <arothe@phosco.info, phosco@gmx.de>
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 *   * Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *   * Redistributions in binary form must reproduce the above copyright notice,
 *     this list of conditions and the following disclaimer in the documentation
 *     and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
 * SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
 * DAMAGE.
 */
if (!defined('HAVE_SELECT_EXPR_PROCESSOR')) {

    require_once(dirname(__FILE__) . '/abstract-processor.php');
    require_once(dirname(__FILE__) . '/expression-list-processor.php');
    require_once(dirname(__FILE__) . '/../expression-types.php');

    /**
     * 
     * This class processes the SELECT expressions.
     * 
     * @author arothe
     * 
     */
    class SelectExpressionProcessor extends AbstractProcessor {

        private $expressionListProcessor;

        public function __construct() {
            $this->expressionListProcessor = new ExpressionListProcessor();
        }

        /**
         * This fuction processes each SELECT clause.
         * We determine what (if any) alias
         * is provided, and we set the type of expression.
         */
        public function process($expression) {
            $tokens = $this->splitSQLIntoTokens($expression);
            $token_count = count($tokens);
            if ($token_count === 0) {
                return null;
            }

            /*
             * Determine if there is an explicit alias after the AS clause. 
             * If AS is found, then the next non-whitespace token is captured as the alias. 
             * The tokens after (and including) the AS are removed.
             */
            $base_expr = "";
            $stripped = array();
            $capture = false;
            $alias = false;
            $processed = false;

            for ($i = 0; $i < $token_count; ++$i) {
                $token = $tokens[$i];
                $upper = strtoupper($token);

                if ($upper === 'AS') {
                    $alias = array('as' => true, "name" => "", "base_expr" => $token);
                    $tokens[$i] = "";
                    $capture = true;
                    continue;
                }

                if (!$this->isWhitespaceToken($upper)) {
                    $stripped[] = $token;
                }

                // we have an explicit AS, next one can be the alias
                // but also a comment!
                if ($capture) {
                    if (!$this->isWhitespaceToken($upper) && !$this->isCommentToken($upper)) {
                        $alias['name'] .= $token;
                        array_pop($stripped);
                    }
                    $alias['base_expr'] .= $token;
                    $tokens[$i] = "";
                    continue;
                }

                $base_expr .= $token;
            }

            $stripped = $this->expressionListProcessor->process($stripped);

            // TODO: the last part can also be a comment, don't use array_pop

            // we remove the last token, if it is a colref,
            // it can be an alias without an AS
            $last = array_pop($stripped);
            if (!$alias && $this->isColumnReference($last)) {

                // TODO: it can be a comment, don't use array_pop

                // check the token before the colref
                $prev = array_pop($stripped);

                if ($this->isReserved($prev) || $this->isConstant($prev) || $this->isAggregateFunction($prev)
                        || $this->isFunction($prev) || $this->isExpression($prev) || $this->isSubQuery($prev)
                        || $this->isColumnReference($prev) || $this->isBracketExpression($prev)) {

                    $alias = array('as' => false, 'name' => trim($last['base_expr']),
                                   'no_quotes' => $this->revokeQuotation($last['base_expr']),
                                   'base_expr' => trim($last['base_expr']));
                    // remove the last token
                    array_pop($tokens);
                    $base_expr = join("", $tokens);
                }
            }

            if (!$alias) {
                $base_expr = join("", $tokens);
            } else {
                /* remove escape from the alias */
                $alias['no_quotes'] = $this->revokeQuotation($alias['name']);
                $alias['name'] = trim($alias['name']);
                $alias['base_expr'] = trim($alias['base_expr']);
            }

            // TODO: this is always done with $stripped, how we do it twice?
            $processed = $this->expressionListProcessor->process($tokens);

            // if there is only one part, we copy the expr_type
            // in all other cases we use "expression" as global type
            $type = ExpressionType::EXPRESSION;
            $no_quotes = $this->revokeQuotation(trim($base_expr));

            if (count($processed) === 1) {
                if (!$this->isSubQuery($processed[0])) {
                    $type = $processed[0]['expr_type'];
                    $base_expr = $processed[0]['base_expr'];
                    $no_quotes = isset($processed[0]['no_quotes']) ? $processed[0]['no_quotes'] : null;
                    $processed = $processed[0]['sub_tree']; // it can be FALSE
                }
            }

            $result = array();
            $result['expr_type'] = $type;
            $result['alias'] = $alias;
            $result['base_expr'] = trim($base_expr);
            if (!empty($no_quotes)) {
                $result['no_quotes'] = $no_quotes;
            }
            $result['sub_tree'] = $processed;
            return $result;
        }

    }
    define('HAVE_SELECT_EXPR_PROCESSOR', 1);
}
